package cz.cuni.amis.pogamut.episodic.schemas;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;

/**
 * <code>SchemaCounter</count> is a structure that counts how many times
 * were certain nodes and set of nodes executed. It is defined by the nodes
 * that contribude to the set. These can be either <code>SchemaEpisodeNode</code>s
 * or <code>SlotContent</code>s. Each counter keeps the list of nodes it is
 * consisting of, its key that is used to index it in map of all schema counters
 * and its actual count number.
 * <p>
 * It is used to compute average episodes and the derivability of nodes from
 * other nodes of given episode.
 *
 * @author Zdenek Behan
 *
 * Based on the original code by Michal Cermak
 */
public class SchemaCounter implements Serializable {
    /**
     * Determines if a de-serialized file is compatible with this class.
     *
     * Maintainers must change this value if and only if the new version
     * of this class is not compatible with old versions. See Sun docs
     * for <a href=http://java.sun.com/products/jdk/1.1/docs/guide
     * /serialization/spec/version.doc.html> details. </a>
     *
     * Not necessary to include in first version of the class, but
     * included here as a reminder of its importance.
     */
    private static final long serialVersionUID = 1L;

    /**
     * Collection of <code>SchemaEpisodeNode</code>s contributing to the set
     * of nodes defining this counter. Can be empty if this is single counter
     * for a <code>SlotContent</code>.
     */
    public Collection<SchemaEpisodeNode> episodeNodes = new HashSet<SchemaEpisodeNode>();

    // Collection of <code>SlotContent</code>s contributing to the set
    public Collection<SlotContent> objectNodes = new HashSet<SlotContent>();

    /**
     * Key under which this counter is stored in the list of all counters in
     * <code>SchemaBag</code>. It is the sum of IDs of all nodes contributing
     * to the set of nodes defining this counter
     */
    final int key;

    /**
     * Actual count number of this counter. It says how many times given nodes
     * where executed together with the use of given objects.
     */
    private int count = 0;

    /**
     * <code>totalCount</code> is similar to <code>count</code>, but it is
     * increased always when the nodes are executed even if it is during
     * the same execution of an action. If duration of an atomic action is
     * 100, its total count will be increased by 100, but its count just by 1.
     */
    private int totalCount = 0;

    /**
     * Instantiate the counter by providing its key and sets of
     * <code>SchemaEpisodeNode</code>s and <code>SlotContent</code>s
     * defining this counter.
     *
     * @param   _key Key that will index this counter in the map of all schema
     * counters. <strong>It is a sum of IDs of all schema nodes and slot
     * contents defining this counter </strong>
     * @param   _schemaNodes Collection of <code>SchemaEpisodeNode</code>s
     * contributing to the set of nodes defining this counter. Can be empty
     * if this is single counter for a <code>SlotContent</code>.
     * @param   _contentNodes Collection of <code>SlotContent</code>s
     * contributing to the set of nodes defining this counter. Can be empty
     * if this is single counter for a <code>SchemaEpisodeNode</code>.
     */
    public SchemaCounter(int level, int index, int _key, Collection<SchemaEpisodeNode> _schemaNodes, Collection<SlotContent> _contentNodes) {
        key = _key;
        episodeNodes.addAll(_schemaNodes);
        objectNodes.addAll(_contentNodes);

        int hash = 0;

        for (SchemaEpisodeNode s : episodeNodes) {
            s.addCount(level, index, key - s.id, this);
            hash += s.id;
        }
        for (SlotContent c : objectNodes) {
            c.addCount(level, index, key - c.id, this);
            hash += c.id;
        }
        assert(hash == key);
        if (hash != key) {
            System.err.println("Incorrect schema counter key!");
        }
    }

    /**
     * Increases the actual count in this counter by one.
     */
    public void increaseCount() {
        count++;
    }

    /**
     * Increases the total count in this counter by one.
     */
    public void increaseTotalCount() {
        totalCount++;
    }

    /**
     * Getter method for the <code>count</code> variable.
     * <p>
     * Difference between <code>count</code> and <code>totalCount</code> is
     * that if execution of some node has longer duration, its count is always
     * increased only once, but its total count is increased in each logic
     * iteration until the execution is finished.
     *
     * @return  Returns actual count number of this counter. It says how
     * many times given nodes where executed together with the use of given objects.
     */
    public int getCount() {
        return count;
    }

    /**
     * Getter method for the <code>totalCount</code> variable.
     * <p>
     * Difference between <code>count</code> and <code>totalCount</code> is
     * that if execution of some node has longer duration, its count is always
     * increased only once, but its total count is increased in each logic
     * iteration until the execution is finished.
     *
     * @return  Returns actual total count number of this counter. It says how
     * many times given nodes where executed together with the use of given objects.
     */
    public int getTotalCount() {
        return totalCount;
    }
    /**
     * Because occasionally two counters can have identical key. It may be needed
     * to check if the retreived counter is actually the wanted one. This
     * method can be used to check if the IDs of nodes it is defined by, all
     * fall into the specified set of integers.
     * <p>
     * Actually nodes defining this counter has to be subset of <code>col</code>.
     *
     * @param col   Set of integers with IDs of nodes/contents defining this counter.
     * @return  Returns true if IDs of all the nodes and slot contents are
     * contained in the specified set. Returns false otherwise (if there is
     * at least one ID not in the specified set).
     */
    public boolean checkNodes(Collection<Integer> col) {
        if (col.size() != episodeNodes.size() + objectNodes.size()) return false;
        for (SchemaEpisodeNode n : episodeNodes) {
            if (!col.contains((Integer)n.id)) return false;
        }
        for (SlotContent c : objectNodes) {
            if (!col.contains((Integer)c.id)) return false;
        }
        return true;
    }

    /**
     * Checks if the counter is co-defined by a specified node. Node can
     * be either schema episode node or slot content node.
     *
     * @param i ID of a node that should be defining this counter.
     * @return  Returns true if specified node is co-defining this counter,
     * returns false otherwise.
     */
    public boolean containsNode(Integer i) {
        for (SchemaEpisodeNode n : episodeNodes) {
            if (n.getId() == i) return true;
        }
        for (SlotContent c : objectNodes) {
            if (c.getId() == i) return true;
        }
        return false;
    }

    /**
     * Because occasionally two counters can have identical key. It may be needed
     * to check if the retreived counter is actually the wanted one. This
     * method can be used to check if the counter is actually defined by
     * the specified sets of <code>SchemaEpisodeNode</code>s and
     * <code>SlotContent</code>s.
     *
     * @param sn    Set of <code>SchemaEpisodeNode</code>s defining this counter.
     * @param cn    Set of <code>SlotContent</code>s defining this counter.
     * @return  Returns true if the counter is actually defined by exactly
     * the specified sets of nodes and counters. Returns false otherwise.
     */
    public boolean checkNodes(Collection<SchemaEpisodeNode> sn, Collection<SlotContent> cn) {
        return (episodeNodes.containsAll(sn) && sn.containsAll(episodeNodes)
                && objectNodes.containsAll(cn) && cn.containsAll(objectNodes));
    }
}
